import random 
import torch 
from d2l import torch as d2l

def synthetic_data(w,b,num_examples):
    '''构造一个数据集'''
    X = torch.normal(0,1,(num_examples,len(w)))
    y = torch.matmul(X,w) + b 
    y += torch.normal(0,0.01,y.shape) # 添加噪声数据
    return X,y

def data_iter(batch_size,features,labels):
    """批量读取数据"""
    num_example = len(features)
    # 打乱数据集
    indices = list(range(num_example))
    random.shuffle(indices)
    for i in range(0,num_example,batch_size):
        batch_indices = torch.tensor(indices[i:min(i+batch_size,num_example)])
        yield features[batch_indices],labels[batch_indices]

def linreg(X,w,b):
    """线性回归模型"""
    return torch.matmul(X,w) + b

def squared_loss(y_hat,y):
    """均方误差损失函数"""
    return (y_hat-y.reshape(y_hat.shape))**2/2

def sgd(params,lr,batch_size):
    """批量随机梯度下降算法"""
    with torch.no_grad():
        for param in params:
            param -= lr*param.grad/batch_size
            param.grad.zero_()

if __name__ == '__main__':
    true_w = torch.tensor([2,-3.4])
    true_b = 4.2
    features,labels = synthetic_data(true_w,true_b,1000)
    print('features:',features[0],'\nlabels:',labels[0])
    batch_size = 10
    for X,y in data_iter(batch_size,features,labels):
        print(X,"\n",y)
        break
    # 训练模型

    # 1 初始化模型参数
    w = torch.normal(0,0.01,size=(2,1),requires_grad=True)
    b = torch.zeros(1,requires_grad=True)

    # 2 初始化超参数
    lr = 0.03
    num_epochs = 10
    net = linreg
    loss = squared_loss

    for epoch in range(num_epochs):
        for X,y in data_iter(batch_size,features,labels):
            # 因为L形状是（batch_size,1),而不是一个标量，L中的所有元素被加到一起，
            # 并以此计算关于[w,b]的梯度
            l = loss(net(X,w,b),y)
            l.sum().backward()
            sgd([w,b],lr,batch_size)
        with torch.no_grad():
            train_l = loss(net(features,w,b),labels)
            print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')

    print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
    print(f'b的估计误差: {true_b - b}')